package com.sfang.common.util.validation;

import com.alibaba.fastjson.JSON;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

public class SignUtils {
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    protected static Logger LOGGER = LoggerFactory.getLogger(SignUtils.class);

    public static String generate(Map<String, String> params, String merchantKey, String merchantSecret) {

        LOGGER.info(String.format("authentication生成签名， params:%s appKey:%s appSecret:%s",
                JSON.toJSONString(params),
                merchantKey,
                merchantSecret));

        Map<String, String> sortedParams = sortMapByKey(params);    //按Key进行排序
        StringBuilder sbParamsValue = new StringBuilder();
        String signature = "";

        for (Map.Entry<String, String> entry : sortedParams.entrySet())
            if (entry.getKey() != null && !entry.getKey().equalsIgnoreCase("sign"))
                sbParamsValue.append(entry.getKey() + "=" + entry.getValue() + "&");

        String joinedParamsValue = sbParamsValue.substring(0, sbParamsValue.length() - 1);
        LOGGER.info("joinedParamsValue:" + joinedParamsValue);

        try {
            String encodedParamsValue = URLEncoder.encode(joinedParamsValue, "UTF-8");
            String message = String.format("%s&%s", "POST", encodedParamsValue);

            String key = merchantKey + "&" + merchantSecret;
            String plainSignature = getHmacSHA1(message, key);
            signature = Base64.getEncoder().encodeToString(plainSignature.getBytes());

        } catch (UnsupportedEncodingException e) {
            LOGGER.error((String.format("authentication生成签名,异常：%s", JSON.toJSONString(e))));
        }

        LOGGER.error((String.format("authentication生成签名,返回数据：%s", JSON.toJSONString(signature))));
        return signature;
    }

    public static String generateSignature(String requestUrl, Map<String, String> params, String merchantKey, String merchantSecret) {

        LOGGER.info(String.format("authentication生成签名，requestUrl：%s params:%s appKey:%s appSecret:%s",
                requestUrl,
                JSON.toJSONString(params),
                merchantKey,
                merchantSecret));

        Map<String, String> sortedParams = sortMapByKey(params);    //按Key进行排序
        StringBuilder sbParamsValue = new StringBuilder();
        String signature = "";

        for (Map.Entry<String, String> entry : sortedParams.entrySet())
            if (entry.getKey() != null && !entry.getKey().equalsIgnoreCase("sign"))
                sbParamsValue.append(entry.getKey() + "=" + entry.getValue() + "&");

        String joinedParamsValue = sbParamsValue.substring(0, sbParamsValue.length() - 1);
        LOGGER.info("joinedParamsValue:" + joinedParamsValue);

        try {
            String encodeRequestUrl = URLEncoder.encode(requestUrl, "UTF-8");
            String encodedParamsValue = URLEncoder.encode(joinedParamsValue, "UTF-8");
            String message = String.format("%s&%s&%s", "POST", encodeRequestUrl, encodedParamsValue);

            String key = merchantKey + "&" + merchantSecret;
            String plainSignature = getHmacSHA1(message, key);
            signature = Base64.getEncoder().encodeToString(plainSignature.getBytes());

        } catch (UnsupportedEncodingException e) {
            LOGGER.error((String.format("authentication生成签名,异常：%s", JSON.toJSONString(e))));
        }

        LOGGER.error((String.format("authentication生成签名,返回数据：%s", JSON.toJSONString(signature))));
        return signature;
    }

    public static String generateSignature(Map<String, String> params, String merchantKey, String merchantSecret) {
        LOGGER.info(String.format("authentication生成签名入参,params:%s merchantKey:%s merchantSecret:%s",
                JSON.toJSONString(params),
                merchantKey,
                merchantSecret));

        Map<String, String> sortedParams = sortMapByKey(params);    //按Key进行c排序
        StringBuilder sbParamsValue = new StringBuilder();
        String signature = "";

        for (Map.Entry<String, String> entry : sortedParams.entrySet())
            if (entry.getKey() != null && !entry.getKey().equalsIgnoreCase("sign"))
                sbParamsValue.append(entry.getKey() + "=" + entry.getValue() + "&");

        String joinedParamsValue = sbParamsValue.substring(0, sbParamsValue.length() - 1);

        try {
            String encodedParamsValue = URLEncoder.encode(joinedParamsValue, "UTF-8");

            String key = merchantKey + "&" + merchantSecret;
            String plainSignature = getHmacSHA1(encodedParamsValue, key);
            signature = Base64.getEncoder().encodeToString(plainSignature.getBytes());
            LOGGER.info(String.format("authentication生成签名返回数据,params:%s merchantKey:%s merchantSecret:%s sign:%s",
                    JSON.toJSONString(params),
                    merchantKey,
                    merchantSecret,
                    signature));
        } catch (Exception e) {
            LOGGER.info(String.format("authentication生成签名异常,params:%s merchantKey:%s merchantSecret:%s error:%s",
                    JSON.toJSONString(params),
                    merchantKey,
                    merchantSecret,
                    JSON.toJSONString(e)));
        }

        return signature;
    }

    public static boolean validateSignature(Map<String, String> params, String merchantKey, String merchantSecret) {

        LOGGER.info(String.format("authentication签名验证，params:%s merchantKey:%s merchantSecret:%s",
                JSON.toJSONString(params),
                merchantKey,
                merchantSecret));

        boolean passed = false;
        Map<String, String> sortedParams = sortMapByKey(params);    //按Key进行排序

        StringBuilder sbParamsValue = new StringBuilder();

        for (Map.Entry<String, String> entry : sortedParams.entrySet())
            if (entry.getKey() != null && !entry.getKey().equalsIgnoreCase("sign"))
                sbParamsValue.append(entry.getKey() + "=" + entry.getValue() + "&");

        String joinedParamsValue = sbParamsValue
                .substring(0, sbParamsValue.length() - 1)
                .replace(" ", "");
        LOGGER.info("joinedParamsValue:" + joinedParamsValue);

        try {
            String encodedParamsValue = URLEncoder.encode(joinedParamsValue, "UTF-8");
            String message = String.format("%s&%s", "POST", encodedParamsValue);


            String key = merchantKey + "&" + merchantSecret;
            String oauthSignature = params.get("sign");
            String signature = getHmacSHA1(message, key);
            String baseSignature = Base64.getEncoder().encodeToString(signature.getBytes());
            LOGGER.info(String.format("encodedParamsValue:%s message:%s key:%s appSignature:%s oauthSignature:%s",
                    encodedParamsValue, message,
                    key, oauthSignature,
                    baseSignature));
            passed = baseSignature.equals(oauthSignature);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return passed;
    }

    public static boolean validate(Map<String, String> params, String merchantKey, String merchantSecret) {
        List<String> excludeFields = Arrays.asList("sign");

        LOGGER.info(String.format("authentication签名验证入参,params:%s merchantKey:%s merchantSecret:%s",
                JSON.toJSONString(params),
                merchantKey,
                merchantSecret));

        boolean passed = false;
        Map<String, String> sortedParams = sortMapByKey(params);    //按Key进行排序

        StringBuilder sbParamsValue = new StringBuilder();

        for (Map.Entry<String, String> entry : sortedParams.entrySet())
            if (!excludeFields.contains(entry.getKey()))
                sbParamsValue.append(entry.getKey() + "=" + entry.getValue() + "&");

        String joinedParamsValue = sbParamsValue.substring(0, sbParamsValue.length() - 1);

        try {
            String encodedParamsValue = URLEncoder.encode(joinedParamsValue, "UTF-8");


            String key = merchantKey + "&" + merchantSecret;
            String oauthSignature = params.get("sign");
            String signature = getHmacSHA1(encodedParamsValue, key);
            String baseSignature = Base64.getEncoder().encodeToString(signature.getBytes());
            passed = baseSignature.equals(oauthSignature);
            LOGGER.info(String.format("authentication签名验证返回数据,params:%s merchantKey:%s merchantSecret:%s passed:%s",
                    JSON.toJSONString(params),
                    merchantKey,
                    merchantSecret,
                    passed));

        } catch (UnsupportedEncodingException e) {
            LOGGER.info(String.format("authentication签名验证异常,params:%s merchantKey:%s merchantSecret:%s error:%s",
                    JSON.toJSONString(params),
                    merchantKey,
                    merchantSecret,
                    JSON.toJSONString(e)));
        }
        return passed;
    }

    /**
     * 使用 Map按key进行排序
     *
     * @param map
     * @return
     */
    public static Map<String, String> sortMapByKey(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }

        Map<String, String> sortMap = new TreeMap<String, String>(
                new Comparator<String>() {
                    public int compare(String str1, String str2) {

                        return str1.compareTo(str2);
                    }
                });

        sortMap.putAll(map);

        return sortMap;
    }

    public static String getHmacSHA1(String src, String key) {
        try {
            LOGGER.info(String.format("getHmacSHA1签名验证，src:%s key:%s", src, key));
            SecretKeySpec signingKey = new SecretKeySpec(key.getBytes("utf-8"), "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            byte[] rawHmac = mac.doFinal(src.getBytes("utf-8"));
            return Hex.encodeHexString(rawHmac);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * @param dataString
     * @param signatureString
     * @param publicKey
     * @return boolean
     * @Desc 使用公钥验签
     * @auth Jimmy
     * @date 下午 15:31 0028 2017/7/28
     */
    public static boolean verifyData(String dataString, String signatureString, PublicKey publicKey)
            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
        if (signatureString == null || "".equals(signatureString)) return false;
        try {
            byte[] signatureBytes = Base64.getDecoder().decode(signatureString);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(dataString.getBytes("UTF-8"));
            return signature.verify(signatureBytes);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @param dataString
     * @param privateKey
     * @return java.lang.String  数据签名
     * @Desc 使用私钥数据签名
     * @auth Jimmy
     * @date 下午 16:12 0027 2017/7/27
     */
    public static String signatureData(String dataString, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateKey);
        signature.update(dataString.getBytes("UTF-8"));
        return new String(Base64.getEncoder().encode(signature.sign()));
    }

    /**
     * 获得公钥
     *
     * @return
     * @throws Exception
     */
    public static PublicKey getPubKey(String pubKeyString) throws Exception {
        pubKeyString = pubKeyString.replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?|\\n?)", "");
        byte[] keyBytes = Base64.getDecoder().decode(pubKeyString);
        // generate public key
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(spec);
        return publicKey;
    }

    /**
     * @param privateKeyString
     * @return java.security.PrivateKey
     * @Desc 获得私钥
     * @auth Jimmy
     * @date 下午 16:11 0027 2017/7/27
     */
    public static PrivateKey getPrivateKey(String privateKeyString) throws Exception {
        privateKeyString = privateKeyString.replaceAll("(-+BEGIN PRIVATE KEY-+\\r?\\n|-+END PRIVATE KEY-+\\r?\\n?|\\n?)", "");
        byte[] keyBytes = Base64.getDecoder().decode(privateKeyString.getBytes());
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance(KEY_ALGORITHM);
        return kf.generatePrivate(spec);
    }

    /**
     * @param data
     * @return byte[]
     * @Desc 使用公钥加密
     * @auth Jimmy
     * @date 上午 11:24 0028 2017/7/28
     */
    public static byte[] encryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
        // 对数据加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }

    /**
     * @param data
     * @param privateKey
     * @return byte[]
     * @Desc 使用私钥加密
     * @auth Jimmy
     * @date 上午 11:24 0028 2017/7/28
     */
    public static byte[] encryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * @param data
     * @param privateKey
     * @return byte[]
     * @Desc 使用私钥解密
     * @auth Jimmy
     * @date 上午 11:24 0028 2017/7/28
     */
    public static byte[] decryptByPrivateKey(byte[] data, PrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    /**
     * @param data
     * @return byte[]
     * @Desc 使用公钥解密
     * @auth Jimmy
     * @date 上午 11:24 0028 2017/7/28
     */
    public static byte[] decryptByPublicKey(byte[] data, PublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return cipher.doFinal(data);
    }
}
